在使用js编程的时候,常常会用到集合对象,集合对象其实是一种泛型,在js中没有明确的规定其内元素的类型,但在强类型语言譬如Java中泛型强制要求指定类型。
ES6引入了iterable类型,Array,Map,Set都属于iterable类型,它们可以使用for…of循环来遍历,都内置forEach方法。
数组
遍历
普通遍历
最简单的一种,也是使用频率最高的一种。
1 2 3 4
| let arr = ['a', 'b', 'c', 'd', 'e'] for (let i = 0; i < arr.length; i++) { console.log(i, ' => ', arr[i]) }
|
优化: 缓存数组长度:
1 2 3 4
| let arr = ['a', 'b', 'c', 'd', 'e'] for (let i = 0, len = arr.length; i < len; i++) { console.log(i, ' => ', arr[i]) }
|
使用临时变量,将长度缓存起来,避免重复获取数组长度,当数组较大时优化效果才会比较明显。
for-in
这个循环很多人爱用,但实际上,经分析测试,在众多的循环遍历方式中它的效率是最低的。
1 2 3 4
| let arr = ['a', 'b', 'c', 'd', 'e'] for (let i in arr) { console.log(i, ' => ', arr[i]) }
|
for-of
这种方式是es6里面用到的,性能要好于forin,但仍然比不上普通for循环。
1 2 3 4 5
| let arr = ['a', 'b', 'c', 'd', 'e'] let index = 0 for (let item of arr) { console.log(index++, ' => ', item) }
|
forEach
数组自带的foreach循环,使用频率较高,实际上性能比普通for循环弱。
1 2 3 4
| let arr = ['a', 'b', 'c', 'd', 'e'] arr.forEach((v, k) => { console.log(k, ' => ', v) })
|
forEach接受第三个参数,指向原数组,没有返回值,对其进行操作会改变原数组对象
1 2 3 4 5 6
| let ary = [12, 23, 24, 42, 1] let res = ary.forEach((item, index, input) => { input[index] = item * 10 }) console.log(res) console.log(ary)
|
如果版本低的浏览器不兼容(IE8-),可以自定义方法实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| Array.prototype.myForEach = function (callback,context) { context = context || window; if('forEach' in Array.prototype) { this.forEach(callback,context) return } for(let i = 0,len = this.length; i < len; i++) { callback && callback.call(context, this[i], i, this) } } let arr = [12, 23, 24, 42, 1] arr.myForEach((v, k) => { console.log(k, ' => ', v) })
|
map
map会返回一个全新的数组,同样接受第三个参数,如果对其进行操作会改变原数组。
1 2 3 4 5 6
| let ary = [12, 23, 24, 42, 1] let res = ary.map((item, index, input) => { return item * 10 }) console.log(res) console.log(ary)
|
如果版本低的浏览器不兼容(IE8-),可以自定义方法实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Array.prototype.myMap = function myMap(callback,context){ context = context || window if('map' in Array.prototype) { return this.map(callback, context) } let newAry = [] for(var i = 0,len = this.length; i < len; i++) { if(typeof callback === 'function') { var val = callback.call(context, this[i], i, this) newAry[newAry.length] = val } } return newAry } arr.myMap((v, k) => { console.log(k, ' => ', v) })
|
过滤
filter
对数组中的每个元素都执行一次指定的函数(callback),并且创建一个新的数组,该数组元素是所有回调函数执行时返回值为 true 的原数组元素。它只对数组中的非空元素执行指定的函数,没有赋值或者已经删除的元素将被忽略,同时,新创建的数组也不会包含这些元素。
1 2 3 4 5
| let arr = [12, 5, 8, 130, 44] let ret = arr.filter((el, index, array) => { return el > 10 }) console.log(ret)
|
map
map也可以作为过滤器使用,不过返回的是对原数组每项元素进行操作变换后的数组,而不是每项元素返回为true的元素集合。
1 2 3 4 5 6
| let strings = ["hello", "Array", "WORLD"] function makeUpperCase(v) { return v.toUpperCase() } let uppers = strings.map(makeUpperCase) console.log(uppers)
|
some
对数组中的每个元素都执行一次指定的函数(callback),直到此函数返回 true,如果发现这个元素,some 将返回 true,如果回调函数对每个元素执行后都返回 false ,some 将返回 false。它只对数组中的非空元素执行指定的函数,没有赋值或者已经删除的元素将被忽略。
1 2 3 4 5 6
| function isBigEnough(element, index, array) { return (element >= 10) } let passed1 = [2, 5, 8, 1, 4].some(isBigEnough) let passed2 = [12, 5, 8, 1, 4].some(isBigEnough)
|
every
对数组中的每个元素都执行一次指定的函数(callback),直到此函数返回 false,如果发现这个元素,every 将返回 false,如果回调函数对每个元素执行后都返回 true ,every 将返回 true。它只对数组中的非空元素执行指定的函数,没有赋值或者已经删除的元素将被忽略。
1 2 3 4 5 6
| function isBigEnough(element, index, array) { return (element >= 10) } let passed1 = [12, 5, 8, 1, 4].every(isBigEnough) let passed2 = [12, 15, 18, 11, 14].every(isBigEnough)
|
排序
1 2 3 4 5
| let arr = ['a', 1, 'b', 3, 'c', 2, 'd', 'e'] console.log(arr) console.log(arr.reverse()) console.log(arr.sort()) console.log(arr)
|
sort自定义排序
1 2 3 4 5 6
| let arr = [1, 100, 52, 6, 88, 99] let arr1 = arr.sort((a, b) => a-b) console.log(arr1) let arr2 = arr.sort((a, b) => b-a) console.log(arr2) console.log(arr)
|
搜索
1 2 3 4
| let arr = [12, 5, 4, 8, 1, 4] arr.indexOf(4) arr.lastIndexOf(4) arr.indexOf(0)
|
增删、清空操作
添加元素
1 2 3 4
| let arr = ['a', 'b', 'c', 'd', 'e'] arr.push(10, 11) arr.unshift(0, 1) console.log(arr)
|
删除元素
1 2 3
| arr.pop() arr.shift() console.log(arr)
|
清空数组
将数组的length设置为0即可
1 2
| let arr = ['a', 1, 'b', 3, 'c', 2, 'd', 'e'] arr.length = 0
|
length详解:
- 因为数组的索引总是由0开始,所以一个数组的上下限分别是:0和length-1;
- 当length属性被设置得更大时,整个数组的状态事实上不会发生变化,仅仅是length属性变大;
- 当length属性被设置得比原来小时,则原先数组中索引大于或等于length的元素的值全部被丢失。
splice
既可以删除也可以添加元素
1 2 3
| let arr = ['a', 'b', 'c', 'd', 'e'] arr.splice(2, 1, 1,2,3) console.log(arr)
|
splice(start, len, elems) : 删除并添加元素(改变原数组)
- start: 起始位置
- len: 删除元素的长度
- elems: 添加的元素队列
- 几种形式:
- splice(start, 0, elems) : 从start位置添加元素
- splice(start, len) : 从start位置删除len个元素
截取、合并与拷贝
1 2 3 4 5 6
| let arr = ['a', 'b', 'c', 'd', 'e'] let arr1 = arr.slice(1, 2) let arr2 = arr.concat([1,2,3]); console.log(arr) console.log(arr1) console.log(arr2)
|
其实slice和concat也可以作为数组的拷贝方法:
1 2
| arr.slice(0) arr.concat()
|
Map
Map是一组键值对的结构,具有极快的查找速度。
创建
方法一: 创建的时候初始化
1 2 3 4 5 6
| let mapObj = new Map([ ['a', 1], ['b', 2], ['c', 3] ]) console.log(mapObj.size)
|
方法二: 创建空Map,之后添加元素
1 2 3 4 5
| let mapObj = new Map() mapObj.set('a', 1) mapObj.set('b', 2) mapObj.set('c', 3) console.log(mapObj.size)
|
注意: Map对象的长度不是length,而是size
基础操作
Map对象的创建、添加元素、删除元素…
1 2 3 4
| mapObj.set('a', 1) mapObj.delete('d') mapObj.has('a') mapObj.get('a')
|
遍历
使用上面创建的Map进行操作
forEach
同数组的forEach遍历,三个参数分别代表: value、key、map本身
1 2 3
| mapObj.forEach((e, index, self) => { console.log(index, ' => ', e) })
|
打印出:
for-of
1 2 3
| for (const e of mapObj) { console.log(e) }
|
打印出:
1 2 3
| ["a", 1] ["b", 2] ["c", 3]
|
注意: for-of遍历出来的是一个数组,其中e[0]为key,e[1]为value
Set
Set和Map类似,但set只存储key,且key不重复。
创建
方法一: 创建的时候初始化
1 2
| let setObj = new Set([1, 2, 3]) console.log(setObj.size)
|
方法二: 创建空Map,之后添加元素
1 2 3 4 5
| let setObj = new Set() setObj.add(1) setObj.add(2) setObj.add(3) console.log(setObj.size)
|
注意: Map对象的长度不是length,而是size
基础操作
1 2 3 4
| let s = new Set([1, 2, 3]) s.add(3) s.delete(3) console.log(s)
|
遍历
使用上面创建的Set进行操作
forEach
Set与Array类似,但Set没有索引,因此回调函数的前两个参数都是元素本身
1 2 3 4
| s1.forEach(function (element, sameElement, set) { console.log(element) })
|
for-of
1 2 3 4
| for (const item of s1) { console.log(item) }
|
类数组对象
JavaScript中,数组是一个特殊的对象,其property名为正整数,且其length属性会随着数组成员的增减而发生变化,同时又从Array构造函数中继承了一些用于进行数组操作的方法。而对于一个普通的对象来说:
如果它的所有property名均为正整数,同时也有相应的length属性,那么虽然该对象并不是由Array构造函数所创建的,它依然呈现出数组的行为,在这种情况下,这些对象被称为“类数组对象”。
形如:
1 2 3 4 5 6
| let obj = { 0: 'qzy', 1: 22, 2: false, length: 3 }
|
类数组对象可以使用Array对象原生方法进行操作。
遍历
沿用上述对象进行操作
forEach
1 2 3
| Array.prototype.forEach.call(obj, function(el, index){ console.log(index, ' => ', el) })
|
map
1 2 3
| Array.prototype.map.call(obj, function(el, index){ console.log(index, ' => ', el) })
|
注意: 类数组对象不支持使用for-of进行遍历,否则会报错: [Symbol.iterator] is not a function
增删截取操作
沿用上述对象进行操作
1 2 3
| Array.prototype.join.call(obj, '-') Array.prototype.slice.call(obj, 1, 2) Array.prototype.push.call(obj, 5)
|
String也是一个类数组对象
由于字符串对象也存在length,且序号从0开始增加,因此字符串也可以看做一个只读的类数组对象,这意味着String对象可以使用Array的所有原型方法。
1 2
| let str = 'hello world' console.log(Array.prototype.slice.call(str, 0, 5))
|
String也可以使用for-of进行遍历
1 2 3 4
| let str = 'hello world' for (const s of str) { console.log(s) }
|
String独有方法
除了使用Array原型对象的方法,String还包含其他一些自己独有的方法:
与Array使用方法相同的方法
搜索: indexOf()、lastIndexOf()、concat()
转换
toLowerCase()、toUpperCase()
截取
substr(start, len)
substring(start, end)
slice(start, end)
1 2 3 4
| let str = 'hello world' let ret1 = str.substr(6, 5) let ret2 = str.substring(6, 11) let ret3 = str.slice(3, 8)
|
substring 是以两个参数中较小一个作为起始位置,较大的参数作为结束位置。
slice 是第一参数为起始位置,第二参数为结束位置,如果结束位置小于起始位置返回空字符串
1
| console.log(str.substring(11, 6) === str.substring(6, 11))
|
接收负数为参数时:
- slice会将它字符串的长度与对应的负数相加,结果作为参数;
- substr则仅仅是将第一个参数与字符串长度相加后的结果作为第一个参数;
- substring则干脆将负参数都直接转换为0。
1 2 3 4 5 6 7 8 9 10
| let str = 'hello world' let ret1 = str.substr(-5) let ret2 = str.substr(-5, 3) let ret3 = str.substring(6, -1) let ret4 = str.slice(6, -1) console.log(ret1 === str.substr(str.length - 5)) console.log(ret2 === str.substr(str.length - 5, 3)) console.log(ret3 === str.substring(6, 0)) console.log(ret4 === str.slice(6, str.length - 1))
|
正则
- match()
- replace()
- search()
- split()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let str = 'hello world' let ret0 = str.match(/r/) let ret1 = str.match(/o/g) let ret2 = str.replace(/o/g, 'e') let ret3 = str.replace(/O/i, 'e') let ret4 = str.search(/l/) let ret5 = str.search(/l/g) let ret6 = str.split(/o|l/) console.log(ret0) console.log(ret1) console.log(ret2) console.log(ret3) console.log(ret4) console.log(ret5) console.log(ret6) console.log(str)
|
转化
Map => Object
1 2 3 4 5 6 7 8
| let mapObj = new Map([ ['a', 1], ['b', 2], ['c', 3] ]) let obj = {} for (const item of mapObj) { obj[item[0]] = item[1] } console.log(obj)
|
Set => Array
1 2 3 4 5 6 7 8
| let setObj = new Set([1, 2, 3]) let arr = [] for (const item of setObj) { arr.push(item) } console.log(arr)
|
Array => String
arr.join(separator)
1 2
| ['a', 'b', 'c', 'd', 'e'].join('')
|
String => Array
str.split(separator)
1
| 'hello world'.split(' ')
|
参考资料
JavaScript中的数组遍历forEach()与map()方法以及兼容写法介绍
JS几种数组遍历方式以及性能分析对比
JavaScript中的类数组对象介绍
数组对象和类数组对象区别
js数组的操作指南
详谈js遍历集合(Array,Map,Set)
JavaScript中的Map、Set及其遍历
JavaScript创建Map对象
slice,substr和substring的区别